/*
  The mbr.c file implements Master Boot Record functions for Hard Disks
*/

#include <globals.h>
#include <klib.h>
#include <linkedlist.h>
#include <panic.h>

#include <hdd.h>

//Private Includes
#include <vfs_info.h>
#include <mbr.h>
#include <fat32.h>

/* Data structures */
static struct partition_type partition_types[] =
{
  {0x00 , "Empty"},
  {0x06 , "FAT-16"},
  {0x07 , "NTFS"},
  {0x0B , "FAT-32"},
  {0x0C , "FAT-32 (LBA)"},
  {0x0E , "FAT-16 (LBA)"},
  {0x0F , "Win95 Extended (LBA)"},
  {0x39 , "Plan 9"},
  {0x82 , "Linux Swap"},
  {0x83 , "Linux"},
  {0x85 , "Linux Extended"},
  {0xA5 , "FreeBSD"},
  {0xA6 , "OpenBSD"},
  {0xA9 , "NetBSD"},
  {0xBE , "Solaris boot"},
  {0xBF , "Solaris"},
  {0xFF , "BBT"}
};


/* Private functions prototypes */

word_t cs_pack(word_t cyl,word_t sec);
void cs_unpack(word_t cyl_sec, word_t *cyl,word_t *sec);
char *partition_type_description(byte_t type);
void print_partition_info(struct partition_entry *pe);
int vfs_mbr_volume(struct partition_entry *pe,int maj,int min,ptr_list_t part_list);

/* Functions implementation */


word_t cs_pack(word_t cyl,word_t sec)
  {
    word_t cyl_low,cyl_high;

    cyl_low = (cyl & 0x0FF)<<8;
    cyl_high = ((cyl>>8) & 0x0FF)<<6;

    return cyl_low | cyl_high | sec;
  }

void cs_unpack(word_t cyl_sec, word_t *cyl,word_t *sec)
{
  word_t high,low;

  high=(cyl_sec>>8) & 0x0FF;
  low=cyl_sec & 0x0FF;

  *cyl = high | ((low & 0x0C0) << 2);
  *sec = cyl_sec & 0x03F; 
}

char *partition_type_description(byte_t type)
{
  int i,found=0;
  char *res=NULL;
  byte_t id;

  for (i=0;!found;i++)
    {
      id=partition_types[i].id;
      
      if (id==type)
	{
	  found=1;
	  res=partition_types[i].description;
	}

      if (id==0xFF)
	break;
    }

  if (!found)
    res="Unknown";

  return res;
}


int vfs_mbr_initialize(int controller,int disk_number,ptr_list_t part_list)
  {
    struct MBR *mbr;
    dword_t minor;

    // allocate MBR structure
    mbr=(struct MBR *)malloc(sizeof(struct MBR));

    hdd_read_sector(disk_number,0,1,(char *)mbr);

    // Partition 0
    minor=disk_to_minor(controller,disk_number,0);
    vfs_mbr_volume(&mbr->partition_0,DEV_MAJOR_HDD_IDE,minor,part_list);

    // Partition 1
    minor=disk_to_minor(controller,disk_number,1);
    vfs_mbr_volume(&mbr->partition_1,DEV_MAJOR_HDD_IDE,minor,part_list);

    // Partition 2
    minor=disk_to_minor(controller,disk_number,2);
    vfs_mbr_volume(&mbr->partition_2,DEV_MAJOR_HDD_IDE,minor,part_list);

    // Partition 3
    minor=disk_to_minor(controller,disk_number,3);
    vfs_mbr_volume(&mbr->partition_3,DEV_MAJOR_HDD_IDE,minor,part_list);

    // Note: extended partitions are not handled yet.

    return 0;
  }

// add a volume to the list
int vfs_mbr_volume(struct partition_entry *pe,int maj,int min,ptr_list_t part_list)
  {
    struct vfs_partition_info *part_info;
    int res;
    // scratch variables
    word_t c,h,s;
    dword_t block;

    part_info=(struct vfs_partition_info *)malloc(sizeof(struct vfs_partition_info));

    // initialize common portion of the partition info structure
    part_info->major = maj;
    part_info->minor = min;
    part_info->type  = pe->type;

    // first block CHS to LBA
    cs_unpack(pe->start_cs,&c,&s);
    h=pe->start_h;
    chs_to_lba(c,h,s,&block);
    part_info->first_block = block;

    // last block CHS to LBA
    cs_unpack(pe->end_cs,&c,&s);
    h=pe->end_h;
    chs_to_lba(c,h,s,&block);
    part_info->last_block = block;

    // driver specific information, will be filled in by driver
    part_info->driver = NULL;
    part_info->custom = NULL;

    switch (pe->type)
      {
	// FAT-32 or FAT-32 (LBA)
      case 0x0B:
      case 0x0C:
	res = vfs_fat32_volume(part_info);
	break;
	
      default:
	// unknown partition type
	res = -1;
      }

    // if a suitable driver has been found
    // then append the partition to the list
    // else discard it
    if (res==0)
      llist_add_last(part_list,part_info);
    else
      free(part_info);

    //printf("Volume: %d [%d] start = %d  end = %d\n",part_info->type,res,part_info->first_block,part_info->last_block);

    return res;
  }


int vfs_mbr_diagnostics(void)
  {
    struct MBR *mbr;
    char *p;
    int i;

    p=(char *)malloc(1024*sizeof(char));
    mbr=(struct MBR *)p;

    // Clear buffer
    for (i=0;i<1024;i++)
      *(p+i)='\0';

    printf("Reading sector... ");
    hdd_read_sector(0,0,1,p);
    printf("done.\n");

    /*
    printf("Contents:\n");
    printf("==================================================\n");
    memory_dump(p,128);
    printf("==================================================\n");
    */

    printf("Partition 0:\n");
    print_partition_info(&mbr->partition_0);
    printf("Partition 1:\n");
    print_partition_info(&mbr->partition_1);
    printf("Partition 2:\n");
    print_partition_info(&mbr->partition_2);
    printf("Partition 3:\n");
    print_partition_info(&mbr->partition_3);

    free(p);

    return 0;
  }

void print_partition_info(struct partition_entry *pe)
  {
    word_t c,h,s;
    dword_t block;

    printf("Type: 0x%x (%s)\n",pe->type,partition_type_description(pe->type));

    // empty
    if (pe->type==0)
      return;

    cs_unpack(pe->start_cs,&c,&s);
    h=pe->start_h;
    chs_to_lba(c,h,s,&block);
    printf("Start: %d (LBA sector)\n",block);    
    cs_unpack(pe->end_cs,&c,&s);
    h=pe->end_h;
    chs_to_lba(c,h,s,&block);
    printf("End: %d (LBA sector)\n",block);
    printf("Size: %d blocks (%d KB)\n",pe->size,pe->size>>1);    
  }

// a value of -1 in partition means whole disk
dword_t disk_to_minor(int controller,int disk,int partition)
  {
    controller &= 0x00FF;
    disk &= 0x00FF;
    partition &= 0x00FF;

    return (controller<<16) | (disk<<8) | partition;
  }

// a value of -1 in partition means whole disk
void minor_to_disk(dword_t minor,int *controller,int *disk,int *partition)
  {
    *controller = (minor>>16) & 0x00FF;
    *disk = (minor>>8) & 0x00FF;
    *partition = minor & 0x00FF;
  }
